import csv
import snappy
from ast import literal_eval
lets import the khovanov homology of the census knots we computed
R.<t,q> = LaurentPolynomialRing(QQ)
import re
from sage.misc.sage_eval import sage_eval
def parse_poly(s):
s = str(s).strip().replace("^", "**").replace(" ", "")
s = re.sub(r'(?<=[0-9])(?=[tq])', '*', s)
s = re.sub(r'(?<=[tq0-9\)])(?=[tq])', '*', s)
return R(sage_eval(s, locals={'t': t, 'q': q}))
KH_data = []
with open('/Users/leomousseau/Desktop/khovanov paper code/Khovanov_census_knots_21_05_26.csv', 'r') as file:
reader = csv.reader(file)
next(reader)
for row in reader:
name = row[0]
free_string = row[4] # kh_integral_unreduced
torsion_string = row[6] # torsion_unreduced
if free_string.strip() == "":
continue
free_part = parse_poly(free_string)
torsion_part = {}
if torsion_string.strip() != "":
for piece in torsion_string.split("|"):
match = re.match(r"Torsion\s*of\s*order\s*(\d+)\s*:\s*(.*)", piece.strip())
order = ZZ(match.group(1))
poly = parse_poly(match.group(2))
torsion_part[order] = poly
khovanov_homology = [free_part, torsion_part]
KH_data.append([name, khovanov_homology])
len(KH_data)
785
def KH(i, j, khovanov_homology):
'''
input: integers (i,j) and the Khovanov homology of a link given by [free_part, torsion_part]
free_part is given by a polynomial in variables t and q, such that
the coefficient of t^i*q^j is the free rank of the Khovanov homology
in bigrading (i,j).
torsion_part is a dictionary. Its keys are torsion orders, and its
values are polynomials in t and q. For example, if
torsion_part[2] has coefficient 3 at t^i*q^j, then there are
three copies of Z/2 in bigrading (i,j).
output: Khovanov homology in bigrading (i,j)
[free_rank, torsion]
free_rank is the rank of the free Z-part in bigrading (i,j).
torsion is a list of torsion orders occurring in bigrading (i,j).
For example, output [1, [2, 2, 5]] means
Z ⊕ Z/2 ⊕ Z/2 ⊕ Z/5.
'''
free_part, torsion_part = khovanov_homology
free_rank = free_part.monomial_coefficient(t^i * q^j)
torsion = []
for order, poly in torsion_part.items():
multiplicity = poly.monomial_coefficient(t^i * q^j)
torsion += [order] * ZZ(multiplicity)
return [free_rank, torsion]
free_part, torsion_part = khovanov_homology
free_rank = free_part.monomial_coefficient(t^i * q^j)
torsion = []
for order, poly in torsion_part.items():
multiplicity = poly.monomial_coefficient(t^i * q^j)
torsion += [order] * ZZ(multiplicity)
return [free_rank, torsion]
Lets import some data about the census knots (positivity, genus, fiberedness status)
positive=[]
with open('positivity.csv', 'r') as file:
reader = csv.reader(file)
for row in reader:
positive.append([row[0],row[1]])
positive=positive[1:]
positivity_dict=dict(positive)
len(positive)
1267
Seifert_genus=[]
with open('3genus.csv', 'r') as file:
reader = csv.reader(file)
for row in reader:
Seifert_genus.append([row[0],row[1]])
Seifert_genus=Seifert_genus[1:]
genus_dict=dict(Seifert_genus)
len(Seifert_genus)
1267
fibered=[]
with open('fibered.csv', 'r') as file:
reader = csv.reader(file)
for row in reader:
fibered.append([row[0],row[1]])
fibered=fibered[1:]
fibered_dict=dict(fibered)
len(fibered)
1267
Lets look at all census knots for which we could not find a positive diagram:
positivity_unclear=[]
for k in positive:
if k[1]!='True':
positivity_unclear.append(k[0])
len(positivity_unclear)
480
Let's implement the obstructions to positivity of Khovanov homology discussed in the paper
def positive_obstructions_from_khovanov(khovanov_homology, genus, fibered):
'''
Tests the Khovanov homology obstructions (listed below) for being a positive knot.
Input:
khovanov_homology = [free_part, torsion_part]
genus = Seifert genus
fibered = True or False
Output:
list of obstruction reasons.
Empty list means: not obstructed by these tests.
lets apply the following obstruction to positivity from khovanov homology:
(1) KH^(i,j)=0 if i<0
(2) KH^(0,2g-1)=F where g is the genus of K (for a link -Eulercharacteristic)
(3) KH^(0,2g+1)=F
(4) KH^(1,2g+1)=F^p1 where p1 is the chromatic number of the reduced Seifert graph of a positive diagram
(5) KH^(1,2g+1)=0 if and only if K is fibered
(6) KH^(0,j)=0 for all other j
(7) KH^(1,j)=0 for all other j
(8) KH^(i,2g-1)=0 for all other i
(9) KH^(0,2g+1)=0 for all other i
'''
free_part, torsion_part = khovanov_homology
g = ZZ(genus)
is_fibered = bool(fibered)
j_low = 2*g - 1
j_high = 2*g + 1
bad = []
# -------------------------
# free part obstructions
# -------------------------
for (i, j), coeff in free_part.dict().items():
if coeff == 0:
continue
# (1) Kh^(i,j)=0 if i<0
if i < 0:
bad.append(f"free part nonzero at negative i: Kh^({i},{j})")
# (6) Kh^(0,j)=0 for all other j
if i == 0 and j not in [j_low, j_high]:
bad.append(f"extra free part in Kh^(0,{j})")
# (7) Kh^(1,j)=0 for all other j
if i == 1 and j != j_high:
bad.append(f"extra free part in Kh^(1,{j})")
# (8) Kh^(i,2g-1)=0 for all other i
if j == j_low and i != 0:
bad.append(f"extra free part in Kh^({i},{j_low})")
# (9) should mean Kh^(i,2g+1)=0 for all other i
if j == j_high and i not in [0, 1]:
bad.append(f"extra free part in Kh^({i},{j_high})")
# -------------------------
# torsion obstructions
# -------------------------
for order, poly in torsion_part.items():
for (i, j), coeff in poly.dict().items():
if coeff == 0:
continue
# (1) Kh^(i,j)=0 if i<0
if i < 0:
bad.append(f"Z/{order} torsion at negative i: Kh^({i},{j})")
# positive knots have prescribed groups in i=0 and i=1.
# These prescribed groups are free, so torsion there obstructs.
if i == 0:
bad.append(f"Z/{order} torsion in Kh^(0,{j})")
if i == 1:
bad.append(f"Z/{order} torsion in Kh^(1,{j})")
# At j = 2g-1 only Kh^(0,2g-1)=Z is allowed
if j == j_low:
bad.append(f"Z/{order} torsion in Kh^({i},{j_low})")
# At j = 2g+1 only possible free groups at i=0,1 are allowed
if j == j_high:
bad.append(f"Z/{order} torsion in Kh^({i},{j_high})")
# -------------------------
# required groups
# -------------------------
# (2) Kh^(0,2g-1)=F, over Z this means free rank 1
if free_part.monomial_coefficient(t^0 * q^j_low) != 1:
bad.append(f"Kh^(0,{j_low}) should have free rank 1")
# (3) Kh^(0,2g+1)=F, over Z this means free rank 1
if free_part.monomial_coefficient(t^0 * q^j_high) != 1:
bad.append(f"Kh^(0,{j_high}) should have free rank 1")
# -------------------------
# fiberedness condition
# -------------------------
rank_Kh1 = sum(
coeff for (i, j), coeff in free_part.dict().items()
if i == 1
)
torsion_Kh1 = sum(
coeff for poly in torsion_part.values()
for (i, j), coeff in poly.dict().items()
if i == 1
)
# (5) Kh^(1,2g+1)=0 iff K is fibered
if is_fibered:
if rank_Kh1 != 0 or torsion_Kh1 != 0:
bad.append("fibered knot: positive would require Kh^(1,*) = 0")
else:
if free_part.monomial_coefficient(t^1 * q^j_high) == 0:
bad.append(f"non-fibered knot: positive would require nonzero Kh^(1,{j_high})")
return bad
We also want to check whether the census knots are negative using our obstructions (a knot is negative if and only if it admits a diagram with only negative crossings. Notice that a link is negative if and only if its mirror is positive).
So let us write functions which give back the khovanov homology of the mirror knot given the khovanov homology of it:
def mirror_free_poly(P):
Q = R(0)
for (i, j), coeff in P.dict().items():
Q += coeff * t^(-i) * q^(-j)
return Q
def mirror_torsion_poly(P):
Q = R(0)
for (i, j), coeff in P.dict().items():
Q += coeff * t^(1-i) * q^(-j)
return Q
def mirror_khovanov_homology(khovanov_homology):
free_part, torsion_part = khovanov_homology
mirror_free = mirror_free_poly(free_part)
mirror_torsion = {
order: mirror_torsion_poly(poly)
for order, poly in torsion_part.items()
}
return [mirror_free, mirror_torsion]
def positive_or_negative_obstructions_from_khovanov(khovanov_homology, genus, fibered):
obs_direct = positive_obstructions_from_khovanov(
khovanov_homology,
genus,
fibered
)
obs_mirror = positive_obstructions_from_khovanov(
mirror_khovanov_homology(khovanov_homology),
genus,
fibered
)
return [obs_direct, obs_mirror]
Let us now apply our obstruction function to the census knots
census_results = []
for name, khom in KH_data:
g = ZZ(genus_dict[name])
fib = (fibered_dict[name] == "True")
diagram_found = (positivity_dict.get(name) == "True")
obs_direct, obs_mirror = positive_or_negative_obstructions_from_khovanov(khom,g,fib)
obstructed_positive = (obs_direct != [])
obstructed_negative = (obs_mirror != [])
if diagram_found:
status = "positive/negative diagram found"
elif obstructed_positive and obstructed_negative:
status = "no diagram found, obstructed"
else:
status = "no diagram found, not obstructed"
census_results.append([
name,
status,
diagram_found,
obs_direct,
obs_mirror
])
Let us summarize the results:
diagram_found_results = [
x for x in census_results
if x[1] == "positive/negative diagram found"
]
obstructed_no_diagram = [
x for x in census_results
if x[1] == "no diagram found, obstructed"
]
survivors = [
x for x in census_results
if x[1] == "no diagram found, not obstructed"
]
print("Integral unreduced KH checked:", len(census_results))
print("Positive/negative diagram found:", len(diagram_found_results))
print("No positive/negative diagram found:", len(obstructed_no_diagram) + len(survivors))
print("No diagram found, obstructed:", len(obstructed_no_diagram))
print("No diagram found, not obstructed:", len(survivors))
print("\nSurvivors:")
print([x[0] for x in survivors])
Integral unreduced KH checked: 785 Positive/negative diagram found: 319 No positive/negative diagram found: 466 No diagram found, obstructed: 449 No diagram found, not obstructed: 17 Survivors: ['s580', 't04651', 't08387', 't12308', 'o9_09214', 'o9_12773', 'o9_19200', 'o9_22182', 'o9_24667', 'o9_25096', 'o9_30634', 'o9_31294', 'o9_35678', 'o9_38702', 'o9_39519', 'o9_41263', 'o9_43369']
We come to the following conclusion:
Out of the 785 census knots for which the khovanov homology (with Z coefficients) is known, their Khovanov homology obstructs 449 from being positive or negative. Among the remaining 336, there are 319 for which positive (or negative) diagrams were found in [BBD+]. In [BBD+], using different obstructions and data from KnotInfo, the 17 remaining knots were found to be not positive. That means that for the 466 census knots which are not positive and not negative, That means that the Khovanov homology obstructions worked for 449 out of the 466 census knots which are not positive or negative.